package simpledb;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * TableStats represents statistics (e.g., histograms) about base tables in a
 * query. 
 * 
 * This class is not needed in implementing lab1 and lab2.
 * 每个table对应一个TableStats
 * TableStats存储表中每个字段对应的直方图IntHistogram/StringHistogram
 *
 */
public class TableStats {

    private static final ConcurrentHashMap<String, TableStats> statsMap = new ConcurrentHashMap<String, TableStats>();

    static final int IOCOSTPERPAGE = 1000;

    public static TableStats getTableStats(String tablename) {
        return statsMap.get(tablename);
    }

    public static void setTableStats(String tablename, TableStats stats) {
        statsMap.put(tablename, stats);
    }
    
    public static void setStatsMap(HashMap<String,TableStats> s)
    {
        try {
            java.lang.reflect.Field statsMapF = TableStats.class.getDeclaredField("statsMap");
            statsMapF.setAccessible(true);
            statsMapF.set(null, s);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    public static Map<String, TableStats> getStatsMap() {
        return statsMap;
    }

    public static void computeStatistics() {
        Iterator<Integer> tableIt = Database.getCatalog().tableIdIterator();
        System.out.println("Computing table stats.");
        while (tableIt.hasNext()) {
            int tableid = tableIt.next();
            TableStats s = new TableStats(tableid, IOCOSTPERPAGE);
            setTableStats(Database.getCatalog().getTableName(tableid), s);
        }
        System.out.println("Done.");
    }

    /**
     * Number of bins for the histogram. Feel free to increase this value over
     * 100, though our tests assume that you have at least 100 bins in your
     * histograms.
     */
    static final int NUM_HIST_BINS = 100;

    //存储每个字段的最大，最小值  仅限于int类型
    private Map<String,int[]> field2max_min;
    //存储每个字段对应的直方图histogram
    private Map<String,Object> field2his;

    private int totalTuples = 0;
    private int ioCostPerPage = IOCOSTPERPAGE;
    private int num_hist_bins = NUM_HIST_BINS;
    //不明确TransactionId的使用，暂时先这样处理
    private TransactionId tid;
    private HeapFile table;
    private TupleDesc tupleDesc;
    /**
     * Create a new TableStats object, that keeps track of statistics on each
     * column of a table
     * 
     * @param tableid
     *            The table over which to compute statistics
     * @param ioCostPerPage
     *            The cost per page of IO. This doesn't differentiate between
     *            sequential-scan IO and disk seeks.
     */
    public TableStats(int tableid, int ioCostPerPage) {
        // For this function, you'll have to get the
        // DbFile for the table in question,
        // then scan through its tuples and calculate
        // the values that you need.
        // You should try to do this reasonably efficiently, but you don't
        // necessarily have to (for example) do everything
        // in a single scan of the table.
        // some code goes here
        field2max_min = new HashMap<>();
        field2his = new HashMap<>();
        this.ioCostPerPage = ioCostPerPage;
        this.tid = new TransactionId();
        this.table = (HeapFile) Database.getCatalog().getDatabaseFile(tableid);
        DbFileIterator iterator = table.iterator(tid);
        this.tupleDesc = table.getTupleDesc();
        try {
            fillField2max_min(iterator);
        } catch (TransactionAbortedException e) {
            e.printStackTrace();
        } catch (DbException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param iterator
     * @throws TransactionAbortedException
     * @throws DbException
     * 填充field2max_min和field2his
     * field2max_min fieldName->max_min[]数组
     * field2his  fieldName->intHistogram/stringHistogram
     */
    private void fillField2max_min(DbFileIterator iterator) throws TransactionAbortedException, DbException {
        iterator.open();
        while(iterator.hasNext())
        {
            Tuple tuple = iterator.next();
            totalTuples++;
            for(int i = 0;i<tupleDesc.numFields();i++)
            {
                if(tuple.getField(i).getType()==Type.STRING_TYPE)
                {
                    continue;
                }
                IntField field = (IntField) tuple.getField(i);
                String fieldName = tupleDesc.getFieldName(i);
                if(field2max_min.containsKey(fieldName))
                {
                    int[] max_min = field2max_min.get(fieldName);
                    int val = field.getValue();
                    if(val>max_min[0])
                    {
                        max_min[0] = val;
                    }
                    if(val<max_min[1])
                    {
                        max_min[1] = val;
                    }
                    field2max_min.put(fieldName,max_min);
                }
                else
                {
                    int val = field.getValue();
                    int[] max_min = new int[2];
                    max_min[0] = val;
                    max_min[1] = val;
                    field2max_min.put(fieldName,max_min);
                }
            }
        }
        //填充int类型的field->historgram
        fillField2his();
        iterator.rewind();
        while(iterator.hasNext())
        {
            Tuple tuple = iterator.next();
            for (int i = 0; i < tupleDesc.numFields(); i++) {
                Type type = tupleDesc.getFieldType(i);
                String fieldName = tupleDesc.getFieldName(i);
                if(type==Type.INT_TYPE)
                {
                        IntField field = (IntField) tuple.getField(i);
                        int value = field.getValue();
                        IntHistogram intHistogram = (IntHistogram) field2his.get(fieldName);
                        intHistogram.addValue(value);
                }
                else
                {
                    StringField field = (StringField) tuple.getField(i);
                    String value = field.getValue();
                    if(field2his.containsKey(fieldName))
                    {
                        StringHistogram stringHistogram = (StringHistogram) field2his.get(fieldName);
                        stringHistogram.addValue(value);
                    }
                    else
                    {
                        StringHistogram stringHistogram = new StringHistogram(num_hist_bins);
                        stringHistogram.addValue(value);
                        field2his.put(fieldName,stringHistogram);
                    }
                }
            }
        }
        iterator.close();
    }

    private void fillField2his()
    {
       for (Map.Entry entry:field2max_min.entrySet())
       {
           String fieldName = (String) entry.getKey();
           int[] max_min = (int[]) entry.getValue();
           IntHistogram intHistogram = new IntHistogram(num_hist_bins,max_min[1],max_min[0]);
           field2his.put(fieldName,intHistogram);
       }

    }


    /**
     * Estimates the cost of sequentially scanning the file, given that the cost
     * to read a page is costPerPageIO. You can assume that there are no seeks
     * and that no pages are in the buffer pool.
     * 
     * Also, assume that your hard drive can only read entire pages at once,
     * 另外，假设您的硬盘一次只能读取整个页面
     * so if the last page of the table only has one tuple on it, it's just as
     * expensive to read as a full page. (Most real hard drives can't
     * efficiently address regions smaller than a page at a time.)
     * 
     * @return The estimated cost of scanning the table.
     */
    public double estimateScanCost() {
        // some code goes here
        return ioCostPerPage*table.numPages();
    }

    /**
     * This method returns the number of tuples in the relation, given that a
     * predicate with selectivity selectivityFactor is applied.
     * 
     * @param selectivityFactor
     *            The selectivity of any predicates over the table
     * @return The estimated cardinality of the scan with the specified
     *         selectivityFactor
     */
    public int estimateTableCardinality(double selectivityFactor) {
        // some code goes here
        return (int) (totalTuples*selectivityFactor);
    }

    /**
     * The average selectivity of the field under op.
     * @param field
     *        the index of the field
     * @param op
     *        the operator in the predicate
     * The semantic语义 of the method is that, given the table, and then given a
     * tuple, of which we do not know the value of the field, return the
     * expected selectivity. You may estimate this value from the histograms.
     * */
    public double avgSelectivity(int field, Predicate.Op op) {
        // some code goes here
        return 1.0;
    }

    /**
     * Estimate the selectivity of predicate <tt>field op constant</tt> on the
     * table.
     * 
     * @param index
     *            The field over which the predicate ranges
     * @param op
     *            The logical operation in the predicate
     * @param constant
     *            The value against which the field is compared
     * @return The estimated selectivity (fraction of tuples that satisfy) the
     *         predicate
     *         计算一次 op val（操作某个字段） 如>5(过滤掉不符合>5的元组)
     *         剩余符合条件的元组/所有元组的比例
     */
    public double estimateSelectivity(int index, Predicate.Op op, Field constant) {
        // some code goes here
        String fieldName = tupleDesc.getFieldName(index);
        Type type = tupleDesc.getFieldType(index);
        if(type==Type.INT_TYPE)
        {
            IntHistogram intHistogram = (IntHistogram) field2his.get(fieldName);
            return intHistogram.estimateSelectivity(op,((IntField) constant).getValue());
        }
        else
        {
            StringHistogram stringHistogram = (StringHistogram) field2his.get(fieldName);
            return stringHistogram.estimateSelectivity(op,((StringField)constant).getValue());
        }
    }

    /**
     * return the total number of tuples in this table
     * */
    public int totalTuples() {
        // some code goes here
        return totalTuples;
    }

}
